R Tutorat

Die Basics für deine BA-Arbeit

Nick Glättli

Organisatorisches

Tutorat in deutscher Sprache (Question in English are welcome!)

Dauer: 01.03.2025 09.00 bis 12.00 Uhr, 08.03.2025 09.00 bis 12.00 Uhr

Pause: Nach Bedarf (Bitte Melden)

Fragen jederzeit stellen!

Slides in html, Code kann in Slides kopiert werden

Voraussetzungen

  • R und RStudio installiert und laufend
  • R up to date (sicher R 4.4.0)
  • Basisverständnis von R und der RStudio Umgebung

Den Rest schauen wir an, ich helfe gerne!

Ziel des Tutorates

  • Vorbereitung auf die BA Arbeit
  • Vermittlung relevanter R-Kenntnisse
  • Rechnung und Interpretation von Regressionen
  • Erstellung von Grafiken
  • Tipps und Tricks

Geballtes Programm!

Limitation des Tutorates

  • Programmieren lernt man nicht im Hörsaal!
  • Fokus auf Anwendung: Für eure BA Arbeit
  • Alles kann ich nicht abdecken

Falls ihre etwas vermisst: Kommt am Mittag auf mich zu!!

Ablauf

Tutorat I

  • Projekt Organisation
  • Daten handhaben
  • Daten visualisieren
  • Lineare Regresssion
  • Lineare Regressionen interpretieren

Tutorat II

  • Kurzer recap
  • Logistische Regression
  • Regressionen und Ergebnisse visualisieren
  • Tips und Tricks
  • Eure Fragen

Projektorganisation

Achtung!

Nehmt diese Grundlagen und Regeln ernst. Es erspart euch viel Zeit und Ärger.

Weshalb R?

  • Vielseitig einsetzbar
  • Schnelle Berechnung
  • Anbindung an andere Dienste

Nachgefragt im Arbeitsmarkt (Industriestandard)

Ansprüche an unseren Code

  • Funktioniert und Ergebnis korrekt
  • Verständlich
  • Repoduzierbar
  • Effizient

Effizienter Code

  • Programmierer sind faul! (oder einfach schlau)
  • Keine Wiederholungen im Code
  • Wenig Code schreiben
  • Berechnungsdauer reduzieren

Funktionen oder Loops nutzen

#Ineffizient
1 + 2 + 3 + 4 + 5 + 6
[1] 21
#Effizient
sum(c(1:6))
[1] 21

Best Practices I

  • Code in Skripten schreiben (Niemals Konsole nutzen!)
  • Resultate stets als Objekt speichern
  • Skripte IMMER in Projekten öffnen

Weshalb? Code muss reproduzierbar sein! Ihr arbeitet nicht bloss einen Tag an eurer Arbeit.

Best Practises II

  • Code direkt kommentieren
  • Code Abschnitte beschreiben ## Überschrift 1 ####

Best Practises III

  • Code übersichtlich gestalten!
  • Code aufteilen in sinnvolle Schritte
  • Zeielnumbrüche innerhalb von Funktionen

Best Practises IV

Projekt organisieren!

Ich empfehle:

  • Data-Ordner
  • Plots-Ordner
  • Tables-Ordner

R-Basics

Klassen

Unsere Daten kommen in verschiedenen Klassen: Numeric, Character und Logical. Zu welcher Klasse ein Datentyp gehört finden wir mit dem class() Befehl heraus.

class(666)
[1] "numeric"
class("Polito")
[1] "character"
class(TRUE)
[1] "logical"

Alternativ:

is.numeric("Polito")
[1] FALSE
is.numeric(666)
[1] TRUE

Logical: Eine spannende Klasse! Auch damit kann man Rechnen.

TRUE+TRUE+FALSE 
[1] 2

Spezialklasse Factor

Factors geben den Ausprägungen eine Reihenfolge.

factor(c("Primarschule", "Sekundarschule", "Universität"))

Factors können unser Leben schnell vereinfachen. Beispielsweise bei Regressionstabellen oder Visualisierungen.

Base R

Mit Base R müssen wir nichts laden. Wir können einfach starten!

#Wurzel
sqrt(9)
[1] 3
#Anzahl Buchstaben
nchar("Olivia")
[1] 6

Libraries

Zusätzliche Packages können wir einfach herunterladen.

#Package downloaden von CRAN
install.packages("tidyverse")
library(tidyverse)

#Package von GitHub
# install.packages("devtools")
devtools::install_github("NickGlaettli/ggthemepark")

GitHub hat viele zusätzliche Packages die nicht auf CRAN sind. Dort suchen lohnt sich!

Hilfe, was ist der Input?

mean(c(1,9, NA, 7))
[1] NA

Und was jetzt?

R hat ein Hilfesystem:

?mean

Internet hilft auch

Vektor

Vektoren beinhalten Informationen, können aber nur einer Klasse angehören.

c(1, 8, "G")
[1] "1" "8" "G"

Aber können wir unsere Aufgaben mit Vektoren lösen? Jein.

Lösung: Daten brauchen Kontext: Kombination von Vektoren zu Data Frames.

Data Frames

Data frames sind im Grunde Tabellen, wie wir sie auch aus Excel kennen.

  • Jede Spalte hat einen Namen (Reihen haben selten auch welche)

  • Eine Spalte ist ein Vektor

  • Spalten können verschiedene Klassen haben. Nur eine Klasse pro Spalte.

  • Reihennummer startet bei 1 (Achtung Python User!)

Spalten auswählen

Spalten werden mit $ ausgewählt. Wenn wir also aus dem iris Datensatz die durchscnittliche Sepal Länge wissen wollen:

mean(iris$Sepal.Length, na.rm = T)
[1] 5.843333

Data Wrangling

Base R oder welche library?

Wieso tidyverse?

  • Vieles wäre auch in Base R oder mit anderen packages möglich

  • tidyverse ist in (fast) allem besser

  • Der Code ist lesbarer und effizienter

Starten mit der Pipe

  • Die Pipe ist vereinfacht dein Code massiv!

  • Sie ist integraler Teil des tidyverse

  • So funktioniert sie: umschliesst Funktion um Funktion

  • Schreibweise: |> oder %>%

Beispiel mit Pipe

library(tidyverse)
#Code without pipes
summarise(group_by(iris, Species), count = n())
# A tibble: 3 × 2
  Species    count
  <fct>      <int>
1 setosa        50
2 versicolor    50
3 virginica     50
#Same code using pipes
iris |>
group_by(Species) |>
summarise(count = n())
# A tibble: 3 × 2
  Species    count
  <fct>      <int>
1 setosa        50
2 versicolor    50
3 virginica     50

Tidy Data

Prinzip von Tidy Data:

  • Jede Spalte ist eine Variable
  • Jede Reihe ist eine Observation
  • Jede Zelle ist ein Wert
head(iris)
  Sepal.Length Sepal.Width Petal.Length Petal.Width Species
1          5.1         3.5          1.4         0.2  setosa
2          4.9         3.0          1.4         0.2  setosa
3          4.7         3.2          1.3         0.2  setosa
4          4.6         3.1          1.5         0.2  setosa
5          5.0         3.6          1.4         0.2  setosa
6          5.4         3.9          1.7         0.4  setosa

Daten importieren

  • Wir nutzen wieder (extended) tidyverse
  • Hauptsächlich readr, readxl und haven
#import csv file
data <- read_csv("datafile.csv")

#import rds file
data <- read_rds("datafile.rds")

#import the third sheet of a excel file (sheet = 1 is the default)
library(readxl)
data <- read_excel("datafile.xlsx", sheet = 3)

#import dta file
library(haven)
data <- read_dta("mydata.dta")

Achtung: Pfad beachten

Daten checken

Immer wichtig ist genau zu wissen wie eure Daten aussehen:

  • Hat es NA?
  • Gibt es Zahlencodes für NA?
  • Gibt es Zahlencodes für Factor?
  • Wurden die Daten korrekt eingegeben?
table(iris$Species, useNA = "always")

    setosa versicolor  virginica       <NA> 
        50         50         50          0 
unique(iris$Species)
[1] setosa     versicolor virginica 
Levels: setosa versicolor virginica
max(iris$Sepal.Length)
[1] 7.9

Variablen auswählen: Select

  • Oft hat ein Datensatz viele hundert Variablen
  • Zur besseren Übersicht wollen wir daher gewisse Variablen auswählen
  • Dazu benutzen wir die Funktion select()
schlegel::selects2015 |>
  select(gender, age, canton, education, vote_choice)

schlegel::selects2015 |>
  select(gender:vote_choice)

Daten sortieren: Arrange

Manchmal müssen wir unsere Daten sortieren. Bspw. um uns besser zu orientieren oder um eine leserliche Tabelle zu exportieren. Dazu nutzen wir arrange().

schlegel::selects2015 |>
  select(gender, age) |>
  arrange(age) |>
  head()
  gender age
1   male  18
2   male  18
3 female  18
4   male  18
5   male  18
6 female  18

By default: Aufsteigend

Was machen wenn wir absteigend sortieren wollen?

schlegel::selects2015 |>
  select(gender, age) |>
  arrange(-age) |>
  head()
  gender age
1 female  96
2 female  95
3   male  94
4   male  94
5   male  93
6   male  92

Oder:

schlegel::selects2015 |>
  select(gender, age) |>
  arrange(desc(age)) |>
  head()
  gender age
1 female  96
2 female  95
3   male  94
4   male  94
5   male  93
6   male  92

Wir können aber natürlich auch strings (character) alphabetisch sortieren.

schlegel::selects2015 |>
  select(gender, age) |>
  arrange(gender) |>
  head()
  gender age
1   male  24
2   male  40
3   male  43
4   male  57
5   male  80
6   male  42

Daten filtern: Filter

Oft müssen wir unsere Daten nach Kriterien filtern. Dazu nutzen wir filter().

schlegel::selects2015 |>
  select(gender, age, canton) |>
  filter(canton == "Zurich") |>
  head()
  gender age canton
1 female  40 Zurich
2 female  28 Zurich
3 female  31 Zurich
4 female  95 Zurich
5 female  74 Zurich
6 female  77 Zurich

Wichtige Operationszeichen

  • “==” gleich
  • “!=” ungleich
  • “>=” grösser gleich
  • “<=” kleiner gleich
  • “>” grösser als
  • “<” kleiner als

Übung select, arrange und filter

Wir nutzen den swiss_popular_bills Datensatz vom schlegel-Package.

  1. Reduziere den Datensatz auf die Variablen date, turnout, no, yes_prop, yes, canton und community. Speicher den neuen Datensatz im Environment.

  2. Wann fand die erste Abstimmung in den Daten statt?

  3. Wie oft war die Zustimmung über 99%?

  4. Welche Gemeinde hatte die höchste Beteiligung im Thurgau?

Lösungen: select, arrange und filter

Aufgabe 1:

popvotes_red <- select(schlegel::swiss_popular_bills, 
                       date, bill, turnout, 
                       yes:yes_prop, canton, 
                       community)

Aufgabe 2:

popvotes_red |>
  arrange(date) |>
  head()
# A tibble: 6 × 8
  date       bill                  turnout yes   no    yes_prop canton community
  <date>     <chr>                 <chr>   <chr> <chr> <chr>    <chr>  <chr>    
1 1981-06-14 Gleichstellung von M… 49.4    181   101   64.2     Zürich Aeugst a…
2 1981-06-14 Gleichstellung von M… 34.3    941   595   61.3     Zürich Affolter…
3 1981-06-14 Gleichstellung von M… 33.8    284   156   64.5     Zürich Hausen a…
4 1981-06-14 Gleichstellung von M… 36.6    297   151   66.3     Zürich Hedingen 
5 1981-06-14 Gleichstellung von M… 51.2    90    97    48.1     Zürich Kappel a…
6 1981-06-14 Gleichstellung von M… 58.9    194   143   57.6     Zürich Knonau   
min(popvotes_red$date)
[1] "1981-06-14"

Aufgabe 3:

popvotes_red |>
  filter(yes_prop > 99)
# A tibble: 10 × 8
   date       bill                 turnout yes   no    yes_prop canton community
   <date>     <chr>                <chr>   <chr> <chr> <chr>    <chr>  <chr>    
 1 1996-03-10 Übertritt Vellerat … 15.6    150   1     99.3     Vaud   Servion  
 2 1996-03-10 Übertritt Vellerat … 52.0    116   1     99.1     Genève Russin   
 3 2002-06-02 Fristenregelung      54.0    119   1     99.2     Vaud   Chavanne…
 4 2006-05-21 Bildungsartikel      17.8    130   1     99.2     Solot… Himmelri…
 5 2006-05-21 Bildungsartikel      36.3    146   1     99.3     Vaud   Bogis-Bo…
 6 2006-05-21 Bildungsartikel      44.5    97    1     99.0     Vaud   Chavanne…
 7 2006-05-21 Bildungsartikel      39.5    271   2     99.3     Vaud   Tannay   
 8 2006-05-21 Bildungsartikel      38.5    126   1     99.2     Vaud   Essertin…
 9 2012-03-11 Regelung der Geldsp… 60.9    152   1     99.3     Vaud   Grancy   
10 2012-03-11 Regelung der Geldsp… 58.2    109   1     99.1     Vaud   Vinzel   

Aufgabe 4:

popvotes_red |>
  filter(canton == "Thurgau") |>
  select(turnout, community) |>
  arrange(desc(turnout)) |>
  head()
# A tibble: 6 × 2
  turnout community      
  <chr>   <chr>          
1 90.4    Hohentannen    
2 89.6    Hüttlingen     
3 89.0    Raperswilen    
4 88.5    Uesslingen-Buch
5 88.1    Neunforn       
6 88.0    Salenstein     

Aber:

popvotes_red |>
  filter(canton == "Thurgau") |>
  select(turnout, community) |>
  arrange(turnout) |>
  head()
# A tibble: 6 × 2
  turnout community      
  <chr>   <chr>          
1 ...     Schlatt (TG)   
2 ...     Warth-Weiningen
3 ...     Mammern        
4 ...     Bottighofen    
5 ...     Bettwiesen     
6 ...     Braunau        

Nicht lösbar? Was müssten wir tun?

Daten transformieren: Mutate

mutate() ist die Basisfunktion für Datentransformationen.

Was mutate macht: Neue Variable

selects2015_red <- select(schlegel::selects2015, gender, 
                          age, canton, education, vote_choice)

selects2015_red |>
  mutate(new_variable = "new") |>
  head()
  gender age     canton                    education vote_choice new_variable
1 female  40     Zurich               apprenticeship        <NA>          new
2 female  58 St. Gallen     higher vocational degree        <NA>          new
3 female  28     Zurich (applied/teacher) university         FDP          new
4   male  24    Thurgau     higher vocational degree         SVP          new
5   male  40    Thurgau               apprenticeship         FDP          new
6 female  31     Zurich (applied/teacher) university         GLP          new
iris |>
  mutate(
    relation_petal = Petal.Length / Petal.Width,
    relation_septal = Sepal.Length / Sepal.Width) |>
  head()
  Sepal.Length Sepal.Width Petal.Length Petal.Width Species relation_petal
1          5.1         3.5          1.4         0.2  setosa           7.00
2          4.9         3.0          1.4         0.2  setosa           7.00
3          4.7         3.2          1.3         0.2  setosa           6.50
4          4.6         3.1          1.5         0.2  setosa           7.50
5          5.0         3.6          1.4         0.2  setosa           7.00
6          5.4         3.9          1.7         0.4  setosa           4.25
  relation_septal
1        1.457143
2        1.633333
3        1.468750
4        1.483871
5        1.388889
6        1.384615
iris2 <- iris
iris2$relation_petal <- iris2$Petal.Length/iris2$Petal.Width
iris2$relation_sepal <- iris2$Sepal.Length/iris2$Sepal.Width
head(iris2)
  Sepal.Length Sepal.Width Petal.Length Petal.Width Species relation_petal
1          5.1         3.5          1.4         0.2  setosa           7.00
2          4.9         3.0          1.4         0.2  setosa           7.00
3          4.7         3.2          1.3         0.2  setosa           6.50
4          4.6         3.1          1.5         0.2  setosa           7.50
5          5.0         3.6          1.4         0.2  setosa           7.00
6          5.4         3.9          1.7         0.4  setosa           4.25
  relation_sepal
1       1.457143
2       1.633333
3       1.468750
4       1.483871
5       1.388889
6       1.384615

case_match()

Wir können jetzt einfache Variablen erstellen. Aber was wenn wir recodieren wollen?

selects2015_red <- selects2015_red |>
  mutate(vote_choice_rec = 
           case_match(
             vote_choice,
             "BDP" ~ "Die Mitte",
             "CVP" ~ "Die Mitte",
             .default = vote_choice
           )
  )
table(selects2015_red$vote_choice, selects2015_red$vote_choice_rec, 
      useNA = "always")
       
        Die Mitte  FDP  GLP  GPS other   SP  SVP <NA>
  SVP           0    0    0    0     0    0  803    0
  FDP           0  688    0    0     0    0    0    0
  BDP         129    0    0    0     0    0    0    0
  CVP         396    0    0    0     0    0    0    0
  GLP           0    0  161    0     0    0    0    0
  SP            0    0    0    0     0  749    0    0
  GPS           0    0    0  252     0    0    0    0
  other         0    0    0    0   409    0    0    0
  <NA>          0    0    0    0     0    0    0 1750

case_when()

Wenn wir Konditionen haben, nutzen wir case_when().

selects2015_red <- selects2015_red |>
  mutate(age_cat = case_when(
    age < 25 ~ "normal",
    age >= 25 & age < 50 ~ "old",
    age >= 50 & age < 65 ~ "very old",
    age >= 65 ~ "practically dead",
    T ~ NA
  ))

table(selects2015_red$age_cat, useNA = "always")

          normal              old practically dead         very old 
             775             1870             1173             1432 
            <NA> 
              87 

Haben wir NA’s gemacht?

Ideen wie wir das checken könnten?

sum(is.na(selects2015_red$age))
[1] 87
summary(selects2015_red$age)
   Min. 1st Qu.  Median    Mean 3rd Qu.    Max.    NA's 
  18.00   33.00   49.00   48.51   63.00   96.00      87 

ifelse()

Wenn unsere Kondition nur zwei Ausprägungen hat, nutzen wir ifelse(). Weil wir faul sind…

selects2015_red <- selects2015_red |>
  mutate(
    geschlecht = ifelse(gender == "male", "Mann", "Frau"))

table(selects2015_red$gender, selects2015_red$geschlecht, useNA = "always")
        
         Frau Mann <NA>
  male      0 2561    0
  female 2776    0    0
  <NA>      0    0    0

Funktioniert aber nur, wenn keine NA vorhanden sind!

Mehrere Mutationen aufs mal

selects2015_red <- selects2015_red |>
  mutate(
    vote_choice_rec = 
           case_match(
             vote_choice,
             "BDP" ~ "Die Mitte",
             "CVP" ~ "Die Mitte",
             .default = vote_choice
           ),
    
    age_cat = case_when(
      age < 25 ~ "normal",
      age >= 25 & age < 50 ~ "old",
      age >= 50 & age < 65 ~ "very old",
      age > 65 ~ "practically dead",
      T ~ NA),
    
    geschlecht = ifelse(gender == "male", "Mann", "Frau"))

Wichtige Funktionen für Klassenwechsel

Oft kommen unsere Daten in falschen Formaten. Bspw. numerische Daten als character.

Hier können wir folgende funktionen gut verwenden:

  • as.numeric()
  • as.character()
  • as.factor()

Mehrere Spalten gleichzeitig mutieren

mtcars |>
  mutate(across(c(drat, wt, qsec), round, 1)) |>
  head()
                   mpg cyl disp  hp drat  wt qsec vs am gear carb
Mazda RX4         21.0   6  160 110  3.9 2.6 16.5  0  1    4    4
Mazda RX4 Wag     21.0   6  160 110  3.9 2.9 17.0  0  1    4    4
Datsun 710        22.8   4  108  93  3.9 2.3 18.6  1  1    4    1
Hornet 4 Drive    21.4   6  258 110  3.1 3.2 19.4  1  0    3    1
Hornet Sportabout 18.7   8  360 175  3.1 3.4 17.0  0  0    3    2
Valiant           18.1   6  225 105  2.8 3.5 20.2  1  0    3    1

Auch hilfreich: everything, starts_with, ends_if

iris |>
  mutate(across(where(is.numeric), round)) |>
  head()
  Sepal.Length Sepal.Width Petal.Length Petal.Width Species
1            5           4            1           0  setosa
2            5           3            1           0  setosa
3            5           3            1           0  setosa
4            5           3            2           0  setosa
5            5           4            1           0  setosa
6            5           4            2           0  setosa

Übung: mutate

Wir arbeiten wieder mit dem popularvotes Datensatz.

  1. Füge eine neue Variable hinzu, die das Jahrhundert angibt.
  2. In der “turnout” Variable gibt es “…”. Mache diese zu NA.
  3. Rechne den turnout von Hand in einer neuen Variable. Kontrolliere die Korrektheit indem du die beiden subtrahierst.

Lösungen: mutate

Aufgabe 1:

popvotes_red <- popvotes_red |>
  mutate(century= case_when(
    year(date) >= 2000 ~ "21st",
    year(date) < 2000 ~ "20th",
    T ~ NA
  ))

head(popvotes_red)
# A tibble: 6 × 9
  date       bill          turnout yes   no    yes_prop canton community century
  <date>     <chr>         <chr>   <chr> <chr> <chr>    <chr>  <chr>     <chr>  
1 1981-06-14 Gleichstellu… 49.4    181   101   64.2     Zürich Aeugst a… 20th   
2 1981-06-14 Gleichstellu… 34.3    941   595   61.3     Zürich Affolter… 20th   
3 1981-06-14 Gleichstellu… 33.8    284   156   64.5     Zürich Hausen a… 20th   
4 1981-06-14 Gleichstellu… 36.6    297   151   66.3     Zürich Hedingen  20th   
5 1981-06-14 Gleichstellu… 51.2    90    97    48.1     Zürich Kappel a… 20th   
6 1981-06-14 Gleichstellu… 58.9    194   143   57.6     Zürich Knonau    20th   

Oder auch weniger effizient:

popvotes_red |>
  mutate(century= case_when(
    year(date) >= "2000-01-01" ~ "21st",
    year(date) < "2000-01-01" ~ "20th",
    T ~ NA
  )) |>
  head()
# A tibble: 6 × 9
  date       bill          turnout yes   no    yes_prop canton community century
  <date>     <chr>         <chr>   <chr> <chr> <chr>    <chr>  <chr>     <chr>  
1 1981-06-14 Gleichstellu… 49.4    181   101   64.2     Zürich Aeugst a… 20th   
2 1981-06-14 Gleichstellu… 34.3    941   595   61.3     Zürich Affolter… 20th   
3 1981-06-14 Gleichstellu… 33.8    284   156   64.5     Zürich Hausen a… 20th   
4 1981-06-14 Gleichstellu… 36.6    297   151   66.3     Zürich Hedingen  20th   
5 1981-06-14 Gleichstellu… 51.2    90    97    48.1     Zürich Kappel a… 20th   
6 1981-06-14 Gleichstellu… 58.9    194   143   57.6     Zürich Knonau    20th   

Aufgabe 2:

popvotes_red <- popvotes_red |>
  mutate(turnout = case_match(
    turnout,
    "..." ~ NA,
    .default = turnout
  ))

sum(is.na(popvotes_red$turnout))
[1] 4033

Aufgabe 3:

popvotes_red <- popvotes_red |>
  mutate(
    yes_num = as.numeric(yes),
    no_num = as.numeric(no),
    yes_prop_num = as.numeric(yes_prop),
    
    yes_prop_2 = (yes_num / (yes_num + no_num))*100,
    test_dif= yes_prop_num - yes_prop_2
  )

sum(abs(popvotes_red$test_dif), na.rm = T)
[1] 17093.46

Wer weiss wo das Problem ist?

popvotes_red |>
  mutate(test_dif= yes_prop_num - round(yes_prop_2, digits = 1)) |>
  select(yes_prop, yes_prop_2, test_dif) |>
  head()
# A tibble: 6 × 3
  yes_prop yes_prop_2 test_dif
  <chr>         <dbl>    <dbl>
1 64.2           64.2        0
2 61.3           61.3        0
3 64.5           64.5        0
4 66.3           66.3        0
5 48.1           48.1        0
6 57.6           57.6        0

Stimmts jetzt?

Daten aggrergieren: summarise

Jetzt wirds spannend. Wir wollen schliesslich aus unseren Daten lernen. Um sie zu aggregieren nutzen wir summarise(). Funktionsweise ist sehr simpel:

selects2015_red |>
  summarise(mean_age = mean(age, na.rm = T))
  mean_age
1  48.5061
selects2015_red |>
  summarise(mean_age = mean(age, na.rm = T),
            median_age = median(age, na.rm = T),
            mean_skew = mean_age - median_age,
            num_resp = n())
  mean_age median_age  mean_skew num_resp
1  48.5061         49 -0.4939048     5337

group_by()

In der Regel wollen wir Werte über Grppen erfahren. Wir müssen unsere Daten also gruppieren:

selects2015_red |>
  group_by(gender) |>
  summarise(mean_age = mean(age, na.rm = T))
# A tibble: 2 × 2
  gender mean_age
  <fct>     <dbl>
1 male       48.3
2 female     48.7

Wir können aber auch nach mehreren Variablen gruppieren:

selects2015_red |>
  group_by(gender, age_cat) |>
  summarise(mean_age = mean(age, na.rm = T)) |>
  na.omit()
# A tibble: 8 × 3
# Groups:   gender [2]
  gender age_cat          mean_age
  <fct>  <chr>               <dbl>
1 male   normal               20.8
2 male   old                  37.9
3 male   practically dead     73.0
4 male   very old             56.4
5 female normal               21.0
6 female old                  38.6
7 female practically dead     73.3
8 female very old             56.9

Manchmal muss man die Daten wieder entgruppieren. Das machen wir mit ungroup().

Nützliche Funktionen zur Aggregation

  • n()
  • count()
  • tally()

Daten Visualisieren

Prinzipien der Datenvisualisierung I

  • Achsen sind SINNVOLL zu beschriften
  • Simpel > Komplex
  • Jeder Aspekt muss eine Berechtigung haben (auch Beschriftungen)
  • Einfach zu lesen

Prinzipien der Datenvisualisierung II

  • Grafiken unterstützen Argumente
  • Irrelevante Informationen sollen rausgenommen werden
  • Keine Pie-Charts!!!!

Was nutzen wir?

Vereinfacht gibt es zwei Möglichkeiten um Plots zu erstellen:

  • Base R

  • ggplot2

Wir nutzen ggplot2 weil:

  • tidyverse (kann in pipeline genutzt werden)

  • einfacherer Code

  • bessere Grafiken

Mit ggplot starten

#library(tidyverse)
#library(ggplot2)

ggplot(iris, aes(x = Sepal.Length, y = Sepal.Width))+
  geom_point()
ggplot(iris, aes(x = Sepal.Length, y = Sepal.Width))+
  geom_point()
  1. Daten spezifizieren (falls in Pipe weglassen)

  2. x und y Werte spezifizieren in der aes Funktion

  3. Bausteine hinzufügen

Bausteinsystem von ggplot

Wer zuerst kommt, malt zuerst

ggplot(data, aes(x= x, y= y))+
  geom_hline(yintercept= 4, color= "red")+
  geom_bar(stat= "identity")
ggplot(data, aes(x= x, y= y))+
  geom_bar(stat= "identity")+
  geom_hline(yintercept= 4, color= "red")

geom_bar vs. geom_col

  • geom_bar benötigt nur x-Werte
  • Per default aggregiert die Funktion die x Werte
  • Wenn ihr das nicht wollt: stat = "identity"
  • Besser: Für barplots geom_col nutzen
ggplot(iris, aes(x= Species))+
  geom_bar()
iris |>
  group_by(Species) |>
  tally() |>
  ggplot(aes(x= Species, y= n))+
  geom_col()

Geoms

Nicht abschliessende Liste:

  • line

  • bar

  • col

  • point

  • boxplot

  • errorbar

  • smooth

Welche Geoms wähle ich? I

  • Daten können verschieden dargestellt werden!
  • Zentrale Frage: Welche Darstellung bildet die Daten und das Argument am besten ab?
  • Geoms können auch kombiniert werden
  • Viele Untertützung im Netz

Welche Geoms wähle ich? II

Diskrete Variablen

  • Barplots (bar/col)

Kontinuierliche Variablen

  • Scatterplots (point)

Zeitreihen

  • Linienplots (line)

Verteilung

  • Boxplots (boxplot)
  • Violinenplots (violin)
  • Dichteplot (density)

Effekte

  • Fehlerbalken (errorbar)
  • Pointrange

Achtung, nicht alle geoms machen was du willst!

ggplot(iris, aes(x = Sepal.Length, y = Sepal.Width))+
  geom_line()
ggplot(iris, aes(x= Sepal.Length, y= Sepal.Width))+
  stat_summary(fun = "mean", geom = "line")
ggplot(iris, aes(x= Sepal.Length, y= Sepal.Width))+
  geom_smooth(se=F, color="black")

Anschreiben der Variablen + Titel

Theoretisch verschiedene Möglichkeiten. Nutzt labs!

ggplot(iris, aes(x= Species))+
  geom_bar()+
  labs(title = "Number of Observations per Species", 
       subtitle = "Counted by Dr. XV",
       x = NULL, y = "number of observations",
       caption = "Source: Iris Dataset")

Gruppieren

Werte nach Spezies darstellen. Wie würdet ihr das machen?

ggplot(iris, aes(x= Sepal.Length, y= Sepal.Width))+
  geom_point()

Farbe (Theorie) I

  • Farben haben einen Zweck!
  • Je dezenter desto besser
  • Beachtet Farbenblindheit!
  • Viele Farbpalletten in R verfügbar

Farbe (Theorie) II

Skalenniveau bestimmt Farbpalette!

Diskret: Qualitative Palette mit unterschiedlichen Farben Kontinuierlich: Divergierende oder Sequenzielle Palette

Siehe hier: https://colorbrewer2.org/

Farbe (Anwendung)

ggplot(iris, aes(x= Sepal.Length, y= Sepal.Width, color = Species))+
  geom_point()

Manuelle Farben

ggplot(iris, aes(x= Sepal.Length, y= Sepal.Width, color = Species))+
  geom_point()+
  scale_color_manual(values = c("darkred", "dodgerblue", "darkorchid4"))

Farbpaletten

Viele Optionen! Wichtigste sind colorbrewer und viridis

ggplot(iris, aes(x= Sepal.Length, y= Sepal.Width, color = Species))+
  geom_point()+
  scale_color_brewer(palette = "Dark2")

Achtung!!!

Gruppierte Farbe gehört in aes()! Allgemeine Farbe ausserhalb!

ggplot(iris, aes(x= Sepal.Length, y= Sepal.Width), color = Species)+
  geom_point()

Ist Farbe notwendig?

  • Zurückhaltung mit Farben
  • Frabe muss auch immer gerechtfertigt sein! (nur für Gruppierung und auch da nur wenn notwendig)
  • Wenn Farbe, dann immer anpassen!
  • Es gibt auch dezente Farbpaletten
  • Beachtet weitere Gruppierungsmöglichkeiten

Color vs. Fill

Es gibt zwei Optionen um einzufärben

  • fill bei Flächen
  • color bei Linien, Punkten usw.

Shape

ggplot(iris, aes(x= Sepal.Length, y= Sepal.Width, shape = Species))+
  geom_point()

Grösse

Hier nicht sinnvoll!

ggplot(iris, aes(x= Sepal.Length, y= Sepal.Width, size = Species))+
  geom_point()

Was mache ich bei vielen Datenpunkten?

Überlappende Datenpunkte verzerren Bild!

  • Jittering
  • Tranzparenz erhöhen

Ein Beispiel

schlegel::selects2015 |>
  ggplot(aes(x = age, y = lr_self))+
  geom_point()

schlegel::selects2015 |>
  ggplot(aes(x = age, y = lr_self))+
  geom_point(alpha = 0.3,
             position = "jitter")

Übung: Plotting

Arbeite mit dem Selects Datensatz. Beschreibe die Achsen sinnvoll.

  1. Plotte die Anzahl Frauen und Männer
  2. Zeige die Altersverteilung

Lösungen: Plotting

Aufgabe 01

ggplot(selects, aes(x= gender))+
  geom_bar()+
  labs(x= "Geschlecht", y= "Anzahl Observationen")

Aufgabe 02

ggplot(selects, aes(x = age))+
         geom_density()

Plots werden schnell unübersichtlich

Der Code

selects |>
  group_by(canton, gender) |>
  summarise(participation_rate = mean(participation, 
                                      na.rm = T)) |>
  ggplot(aes(x = canton, y = participation_rate,
             fill = gender))+
  geom_col(position = "dodge")+
  scale_y_continuous(labels = scales::percent)+
  theme(axis.text.x = element_text(angle = 50, 
                                   hjust = 1))

Lösung: Small multiples

selects |>
  group_by(canton, gender) |>
  summarise(participation_rate = mean(participation, 
                                      na.rm = T)) |>
  ggplot(aes(x = gender, y = participation_rate,
             fill = gender))+
  geom_col()+
  scale_y_continuous(labels = scales::percent)+
  facet_wrap(~ canton)

Daten sortieren

Bei der Datenvisulaisierung gilt: IMMER SINNVOLL SORTIEREN!!

Ideen wo im Code man ansetzen muss?

Daten sortieren

mtcars |>
  group_by(cyl) |>
  tally() |>
  ggplot(aes(x= reorder(cyl, n), y= n))+
  geom_col()
mtcars |>
  group_by(cyl) |>
  tally() |>
  ggplot(aes(x= reorder(cyl, -n), y= n))+
  geom_col()

Ebenfalls möglich: fct_reorder

Wird genau gleich benutzt

Labels leserlich machen

Problem: Wir können die Beschriftungen nicht lesen

ggplot(USArrests, aes(y= UrbanPop, 
                      x= reorder(rownames(USArrests), UrbanPop)))+
  geom_col()

Labels leserlich machen

Wir lösen das Problem indem wir Anpassungen im theme vornehmen.

ggplot(USArrests, aes(y= UrbanPop,
                      x= reorder(rownames(USArrests), UrbanPop)))+
  geom_col()+
  theme(axis.text.x = element_text(angle = 50, 
                                   hjust = 1),
        axis.title.x = element_blank())

Get the details right!

Grafiken sind nicht das wichtigste! Aber…

  • Theme benutzen (allenfalls eigenes gestalten)
  • Prozentzeichen einfügen (scale_y_continous(labels = scales::percent))
  • Legende verschieben
  • Font anpassen

Übung: Nachplotten

Lösung: Nachplotten

selects |>
  mutate(education2 = case_match(education, 
                              "no graduation" ~ "low education",
                              "compulsory schooling" ~ "low education",
                              "apprenticeship" ~ "low education",
                              "diploma or trading school" ~ "medium education",
                              "(vocational) matura" ~ "medium education",
                              "higher vocational degree" ~ "medium education",
                              "HF" ~ "medium education",
                              "(applied/teacher) university" ~ 
                                "high education")) |>
  group_by(canton, gender, education2) |>
  tally() |>
  na.omit() |>
  group_by(canton, gender) |>
  mutate(perc = n/ sum(n)) |>
  ggplot(aes(x= education2, y= perc, fill= gender))+
  geom_col(position = "dodge")+
  facet_wrap(~canton)+
  scale_y_continuous(labels = scales::percent)+
  scale_fill_manual(values = c("dodgerblue", "darkred"),
                    labels = c("Männer", "Frauen"))+
  labs(x= NULL, y= NULL, fill = "Geschlecht",
       caption = "low bis apprenticeship; medium bis HF; high alles darüber")+
  theme_bw()+
  theme(legend.position = "bottom",
        axis.text.x = element_text(angle = 90, hjust = 1))

Statistik

Es kommt alles zusammen

  • Data wrangling
  • Visualisierung
  • (neu) Modelle rechnen
  • (neu) Effekte berechnen und visualisieren

Lineare Regression rechnen und interpretieren

model_lm <- lm(Sepal.Length ~ Sepal.Width + Species, 
               data = iris)
summary(model_lm)

Call:
lm(formula = Sepal.Length ~ Sepal.Width + Species, data = iris)

Residuals:
     Min       1Q   Median       3Q      Max 
-1.30711 -0.25713 -0.05325  0.19542  1.41253 

Coefficients:
                  Estimate Std. Error t value Pr(>|t|)    
(Intercept)         2.2514     0.3698   6.089 9.57e-09 ***
Sepal.Width         0.8036     0.1063   7.557 4.19e-12 ***
Speciesversicolor   1.4587     0.1121  13.012  < 2e-16 ***
Speciesvirginica    1.9468     0.1000  19.465  < 2e-16 ***
---
Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1

Residual standard error: 0.438 on 146 degrees of freedom
Multiple R-squared:  0.7259,    Adjusted R-squared:  0.7203 
F-statistic: 128.9 on 3 and 146 DF,  p-value: < 2.2e-16

Regressionen interpretieren

Veränderungen zwischen Modellen sind hochrelevant!

Regressionen mit Umfragedaten

Die richtigen Gewichte verwenden!

survey_model <- lm(lr_self ~ gender + age, 
               data = schlegel::selects2015,
               weights = weight_total)
summary(survey_model)

Call:
lm(formula = lr_self ~ gender + age, data = schlegel::selects2015, 
    weights = weight_total)

Weighted Residuals:
     Min       1Q   Median       3Q      Max 
-12.1728  -1.4492  -0.0237   1.3817  10.4354 

Coefficients:
              Estimate Std. Error t value Pr(>|t|)    
(Intercept)   5.244635   0.108282  48.435  < 2e-16 ***
genderfemale -0.674301   0.072140  -9.347  < 2e-16 ***
age           0.011283   0.002001   5.640  1.8e-08 ***
---
Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1

Residual standard error: 2.448 on 4711 degrees of freedom
  (623 observations deleted due to missingness)
Multiple R-squared:  0.02429,   Adjusted R-squared:  0.02388 
F-statistic: 58.65 on 2 and 4711 DF,  p-value: < 2.2e-16

Interaktionsterme

model_lm2 <- lm(lr_self ~ age*gender, data = selects)
summary(model_lm2)

Call:
lm(formula = lr_self ~ age * gender, data = selects)

Residuals:
    Min      1Q  Median      3Q     Max 
-6.0414 -1.7592  0.1535  2.0226  5.6773 

Coefficients:
                  Estimate Std. Error t value Pr(>|t|)    
(Intercept)       5.134430   0.151725  33.840  < 2e-16 ***
age               0.010425   0.002918   3.572 0.000357 ***
genderfemale     -1.204561   0.213766  -5.635 1.85e-08 ***
age:genderfemale  0.011399   0.004100   2.780 0.005457 ** 
---
Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1

Residual standard error: 2.594 on 4710 degrees of freedom
  (623 observations deleted due to missingness)
Multiple R-squared:  0.02937,   Adjusted R-squared:  0.02876 
F-statistic: 47.51 on 3 and 4710 DF,  p-value: < 2.2e-16

Quadrieren

Anpassung notwendig, damit R checkt, dass wir quadrieren wollen: I(variable^2)

Oder neue quadrierte variable vorher erstellen.

model_lm3 <- lm(lr_self ~ age + I(age^2) + gender, data = selects)

Regressionstabelle exportieren

Problem: R Output ist nicht schön

Lösung: Tabelle als html oder LaTeX exportieren

Entweder Stargazer oder Texreg eignen sich gut.

stargazer::stargazer(model_lm, 
                     type = "html", 
                     out = "regressiontable.html")

Regressionstabelle exportieren

Unbedingt immer Koeffizientennamen leserlich machen und sortieren!

texreg::texreg(model_lm, 
               custom.coef.names = c("Sepal Length", 
                                     "Versicolor", 
                                     "Virginica"))

Fragen

Themen fürs nächste Mal

https://forms.gle/vQ5YJmVNAJ14LJzz6

Vielen Dank für eure Aufmerksamkeit

Statistik II

Logit modell

Bei binären abhängigen Variablen rechnen wir ein logit Modell. Wieso?

logit_model <- glm(participation ~ gender*age,
                   data = selects,
                   family = binomial)
summary(logit_model)

Call:
glm(formula = participation ~ gender * age, family = binomial, 
    data = selects)

Coefficients:
                  Estimate Std. Error z value Pr(>|z|)    
(Intercept)      -0.656152   0.126185  -5.200 1.99e-07 ***
genderfemale      0.265886   0.171467   1.551 0.120983    
age               0.036530   0.002728  13.390  < 2e-16 ***
genderfemale:age -0.012976   0.003600  -3.604 0.000313 ***
---
Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1

(Dispersion parameter for binomial family taken to be 1)

    Null deviance: 6332.8  on 5197  degrees of freedom
Residual deviance: 6007.8  on 5194  degrees of freedom
  (139 observations deleted due to missingness)
AIC: 6015.8

Number of Fisher Scoring iterations: 4

Effekt berechnen

Logit Modelle können nicht direkt interpretiert werden. Lösung: Vorhergesagte Wahlscheinlichkeiten!

library(glm.predict)
predicts(logit_model, "F;Median", type = "simulation")
       mean     lower     upper gender age
1 0.7562157 0.7390932 0.7740080   male  49
2 0.6822708 0.6651122 0.7006634 female  49

Effekt berechnen

Discrecte changes sind auch nützlich!

predicts(logit_model, "F;Median", type = "simulation", position = 1)
  val1_mean val1_lower val1_upper val2_mean val2_lower val2_upper    dc_mean
1  0.756585  0.7389667  0.7742643 0.6822919  0.6652876  0.7001859 0.07429314
    dc_lower  dc_upper gender_val1 gender_val2 age
1 0.04965252 0.1002239        male      female  49

Effekte Visualisieren

predicts(logit_model, "F; 20-80,5", type = "simulation") |>
  ggplot(aes(x= age, y= mean,
             ymin= lower, ymax= upper,
             color= gender, fill= gender))+
  geom_ribbon(alpha=0.3)+
  geom_line()+
  scale_y_continuous(labels = scales::percent)

Mehrere Modelle in eine Regressionstabelle

texreg::texreg(list(model_lm, model_lm2, logit_model))

T-Test output interpretieren

t.test(iris$Sepal.Length, iris$Sepal.Width)

    Welch Two Sample t-test

data:  iris$Sepal.Length and iris$Sepal.Width
t = 36.463, df = 225.68, p-value < 2.2e-16
alternative hypothesis: true difference in means is not equal to 0
95 percent confidence interval:
 2.63544 2.93656
sample estimates:
mean of x mean of y 
 5.843333  3.057333 
t.test(iris$Sepal.Length, iris$Sepal.Width,
       alternative = "greater")

    Welch Two Sample t-test

data:  iris$Sepal.Length and iris$Sepal.Width
t = 36.463, df = 225.68, p-value < 2.2e-16
alternative hypothesis: true difference in means is greater than 0
95 percent confidence interval:
 2.659806      Inf
sample estimates:
mean of x mean of y 
 5.843333  3.057333 
t.test(iris$Sepal.Length, iris$Sepal.Width,
       alternative = "less")

    Welch Two Sample t-test

data:  iris$Sepal.Length and iris$Sepal.Width
t = 36.463, df = 225.68, p-value = 1
alternative hypothesis: true difference in means is less than 0
95 percent confidence interval:
     -Inf 2.912194
sample estimates:
mean of x mean of y 
 5.843333  3.057333 

HelpR

Wo finde ich hilfe?

  • Man kann nicht alles wissen!
  • Google ist euer Freund
  • Viel Information auf Webseiten, Foren, YouTube usw.

Nicht verzweifeln, einfach mal googeln!

Was ist mit ChatGPT?

Mittlerweile gutes Tool

  • Zuerst mit Hilfe von Google versuchen!
  • ChatGPT nutzen bevor Stackoverflow gefragt wird
  • Kritisch bleiben, Code verstehen!

ChatGPT kann auch helfen Code von Google usw zu verstehen!

Persönlichen Austausch nicht vergessen!

  • Sich mit anderen Personen auszutauschen ist sehr hilfreich
  • Neue Perspektive, neue Ideen
  • Auch die Methodik könnt ihr so kritisch diskutieren

Tips und Tricks

Struktur der Arbeit

  • Einleitung
  • Literatur und Theorie
  • Methoden
  • Resultate
  • Diskussion
  • Schlussfolgerungen
  • Literaturverzeichnis
  • Anhang

Viele Wege führen nach Rom.

Zitieren

Nutzt Zitiersoftware!

  • Zotero bei Word
  • LaTeX

Wieso LaTeX?

  • Wissenschaftlicher Standard
  • Einfache Automatisierung
  • Grosse Flexibilität
  • Einfaches zitieren mit Google Scholar
  • Gute Integration der Regressionstabellen

Overleaf nutzen!

Anhang: Wofür und was gehört da rein?

Jede Grafik und Tabelle in der Arbeit untertützt euer Argument!

  • Was nichts zur “Story” beiträgt, gehört in den Anhang
  • Alle Modelle müssen mit einer Regressionstabelle in der Arbeit oder Anhang dargestellt werden
  • Erstellt viele Plots, aber die meisten kommen in den Anhang

Anhang dient zur vertieften Information

Ein Beispiel

Modelle definieren

Theorie bestimmt!

  • Modelle nach und nach aufbauen
  • Parsimonität!
  • Standard Kontrollvariablen (Alter, Geschlecht, Bildung, Einkommen, Ideologie)
  • Immer überlegen: Macht das Sinn? Ist das notwendig?

Was sind robustness checks?

Rubustness checks unterstützen euere Resultate mit anderen Daten

  • Sie liefern mehr Belege für die Validität eurer Resultate
  • Andere Daten, Variablen, Gewichte usw.

Beispiel meiner MA-Arbeit:

  • Neue Gewichtung der Umfragedaten mit anderen Bevölkerungsverteilungen
  • Keine getrimmiten Gewichte
  • Andere proxy Variablen

Fokus der Arbeit

  • Theoretische Herleitung eurer Hypothesen
  • Solide Methodik
  • Interpretation eurer Ergebnisse

Ergebnisse interpretieren

Präsentation der puren Ergebnisse kann oft kurz gehalten werden.

Fragen: - Was heisst das bezgl. meiner Hypothesen? - Was sind mögliche Erklärungen? - Stimmen sie mit der Literatur überein?

kreativer Prozess think critically!!

Was ist mit Null-Results?

Signifikanz ist nicht alles!

  • Ist die fehlende Signifikanz überraschend?
  • Woran liegt es? (Theorie, Methodik, Daten)
  • Gibt es theoretische Erklärungen für die fehlende Signifikanz?

Codebooks

Schaut euch die Codebooks ganz genau an!

Fragen

Vielen Dank für eure Aufmerksamkeit